class Plan < ActiveRecord::Base
  belongs_to :student
  # Forgetting to declare this relationship was a really insidious bug
  has_many :planned_courses
  has_many :courses, :through => :planned_courses
  has_many :comments

  def display_name
    return "#{student.last_name}, #{student.first_name} (#{self.id})"
  end

  def te_hours(category)
    raise "Invalid TE category" unless [:math, :sci, :engsci, :engdes].include?(category)
    prop = "te_#{category}".to_sym

    return self.courses.inject(0) {|m,c| m + c.send(prop)}
  end

  def cse_count(category)
    raise "Invalid CSE category" unless [:a, :b, :c, :d].include?(category)
    prop = "cse_#{category}".to_sym

    return self.courses.select { |c| c.send(prop) > 0 }.size
  end

  def num_courses
    return courses.size
  end

  def min_courses_per_term
    # This is ugly, but potent; functional stuff is fun. :3
    p_courses = planned_courses.map {|c| c.term}
    f = p_courses.inject(Hash.new(0)) { |m,t| m[t] += 1; m }
    return f.to_a.sort_by {|a| a[1]}[0][1]
  end

  def validate_ceab
    errors = validate_plan

    if errors.empty?
      self.isValid = true
    else
      self.isValid = false
    end

    return errors
  end

  private
  def validate_plan
    # I think we could use a fancy-pants rules engine like ruleby here, but
    # it's pretty much overkill.
    #
    errors = {}

    min_course_load = (self.min_courses_per_term - deg_req.courses_per_term)
    if min_course_load < 0
      errors[:too_few_courses] = min_course_load.abs
    end

    # Pull in the error hashes from the TE and CSE validators
    errors.merge! validate_te()
    errors.merge! validate_cse()

    return errors
  end

  def deg_req
    return self.student.degree_requirement
  end

  def validate_te
    errors = {}

    math = self.te_hours(:math) - deg_req.min_math_sci
    sci = self.te_hours(:sci) - deg_req.min_math_sci
    math_sci = math + sci

    engsci = self.te_hours(:engsci) - deg_req.min_engsci_des
    engdes = self.te_hours(:engdes) - deg_req.min_engsci_des
    engsci_des = engsci + engdes

    minimum_te_course_hours = [
      [math, :math],
      [sci, :sci],
      [engsci, :engsci],
      [engdes, :engdes]
    ]

    # Check minimum number of course hours per 'field'
    minimum_te_course_hours.each do |c|
      shortfall, requirement = c
      error_sym = "#{requirement}_too_low".to_sym
      if shortfall < 0
        errors[error_sym] = shortfall.abs
      end
    end

    # Check total number of course hours
    if math_sci < deg_req.extra_math_sci
      errors[:math_sci_too_low] = (deg_req.extra_math_sci - math_sci)
    end

    if engsci_des < deg_req.extra_engsci_des
      errors[:engsci_des_too_low] = (deg_req.extra_engsci_des - engsci_des)
    end

    return errors
  end

  def other_cse_counts(list_a, list_c, list_d)
    a = [list_a, 0].max
    c = [list_c, 0].max
    return a + c + list_d
  end

  def validate_cse
    errors = {}
    extra_a = self.cse_count(:a) - deg_req.cse_a
    extra_b = self.cse_count(:b) - deg_req.cse_b
    extra_c = self.cse_count(:c) - deg_req.cse_c
    extra_d = other_cse_counts(extra_a, extra_c, self.cse_count(:d)) - deg_req.cse_other

    cse_selections = [
      [extra_a, :list_a],
      [extra_b, :list_b],
      [extra_c, :list_c],
      [extra_d, :list_other]
    ]

    cse_selections.each do |sel|
      value, requirement = sel
      if value < 0
        error_sym = "#{requirement}_too_low".to_sym
        errors[error_sym] = value.abs
      end
    end

    return errors
  end

end
